package tom.adminlte.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.annotation.WebListener;

/**
 * spring-security安全配置， 初始化流程参见父类{@link WebSecurityConfigurerAdapter}
 *
 * @author ZHUFEIFEI
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * lazy注解在此处使用效果：当存在对sessionRegistry的使用时再做初始化和注入操作
     * 以解决redisOperation注入问题
     */
    @Lazy
    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置用户信息加载服务类
        auth.userDetailsService(this.userDetailsService);
        //继承DaoAuthenticationProvider，可以自定义认证的实现
//        auth.authenticationProvider()
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        //静态资源忽略安全认证
        web.ignoring().antMatchers("/static/**");
//        web.debug(true);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置访问规则，使用默认规则，允许表单登陆和http basic，并且所有请求需要登陆
        http.headers().cacheControl()
                //防frame嵌套
                .and().frameOptions().sameOrigin()
                //跨域
                .and().cors();

        http.sessionManagement()
                //同一个用户同一时间直只允许登陆一个，如果再次登陆则上一次session失效
                .maximumSessions(1)
                .expiredUrl("/login?expired")
                //内存中的session管理交给Spring-session-data-redis
                .sessionRegistry(this.sessionRegistry)
                .and()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

        //.Spring Security 4.0之后，引入了CSRF，默认是开启。CSRF默认支持的方法： GET|HEAD|TRACE|OPTIONS，不支持POST
        //客户端发起第一次get请求时，服务器CsrfFilter会给cookie设置一个值(XSRF-TOKEN)
        // ，post请求时需要把该值hidden域提交上来,
        //可以禁用，spring4默认时启用的。如果启用的话,需要一并提交服务器返回的xsrf-token
        //提交方式1.header形式key为X-CSRF-TOKEN 2. 参数形式的key为：_csrf
//        http.csrf().disable();
        //CookieCsrfTokenRepository 可以基于session基于cookie有多种实现, 此处基于session
        http.csrf().csrfTokenRepository(new HttpSessionCsrfTokenRepository());

        http.exceptionHandling().accessDeniedPage("/403");

        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/index").failureUrl("/login?error").permitAll()
                .and()
                .logout().logoutUrl("/logout")
                //设置了csrf之后，LogoutConfigurer中配置仅支持POST类型的logout, 这里增加GET
                .logoutRequestMatcher(new OrRequestMatcher(
                        new AntPathRequestMatcher("/logout", "GET"),
                        new AntPathRequestMatcher("/logout", "POST")
                ))
                //默认就是true
                .invalidateHttpSession(true).clearAuthentication(true)
                ;

    }

    /**
     * 创建明文密码加密解密工具bean, 在本类初始化完成后会在ApplicationContext中
     * 查找可用的PasswordEncoder对象用于密码验证和加密。
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * sessionManagement 使用session监听器发布session创建和销毁事件
     *
     * @return
     */
    @Bean
    public HttpSessionEventPublisher sessionListener() {
        return new HttpSessionEventPublisher();
    }
}
